<?php

/**
 * Active Query
 * @author ReGeDa
 */
class ActiveQuery extends Plugin_Object {

	/**
	 * Binding parameters
	 * @var array
	 */
	private $params = array();

	/**
	 * Where condition
	 * @var array
	 */
	private $condition = array();

	/**
	 * Selection fields
	 * @var array
	 */
	private $fields = array();

	/**
	 * Input data
	 * @var array
	 */
	private $input = array();

	/**
	 * Tables
	 * @var array
	 */
	private $tables = array();

	/**
	 * Order By query
	 * @var array
	 */
	private $order = array();

	/**
	 * Escape by double quotes
	 * @param string $var
	 * @param boolean $escape
	 * @return string
	 */
	private function escape(&$var, $escape = TRUE) {
		return $escape ? '"'.addslashes($var).'"' : $var;
	}

	/**
	 * Escape param for bind
	 * @param mixed $var
	 * @param boolean $escape
	 * @return string
	 */
	private function escape_param(&$var, $escape = TRUE) {
		if ($escape) {
			array_push($this->params, $var);
			return '?';
		}
		else
			return $var;
	}

	/**
	 * Constructor
	 */
	public function  __construct() {
		parent::__construct();
	}

	/**
	 * Set tables selector
	 * @param string $table
	 * @param boolean $escape
	 * @return ActiveQuery
	 */
	public function from($table, $escape = TRUE) {
		array_push($this->tables, $this->escape($table, $escape));
		return $this;
	}

	/**
	 * Set order rule
	 * @param string|array $field - column:(asc|desc)
	 * @return ActiveQuery
	 */
	public function order($field) {
		// multiple setter
		if (is_array($field)) {
			foreach($field as $column)
				$this->order($column);
		}
		else {
			if (preg_match('/^(\w+)(:(asc|desc))?$/',$field,$matches))
				$this->order[$matches[1]] = empty($matches[3]) ? 'asc' : $matches[3];
			else
				throw new Platcode_Exception("Failed order '$field' rule");
		}
		// cascade self
		return $this;
	}

	/**
	 * Set primary table
	 * @param string $table
	 * @param boolean $escape
	 * @return ActiveQuery
	 */
	public function into($table, $escape = TRUE) {
		$this->tables[0] = $this->escape($table, $escape);
		return $this;
	}

	/**
	 * Set field for manipulation data
	 * @param mixed $var
	 * @param mixed $value
	 * @param boolean $escape
	 * @return ActiveQuery
	 */
	public function set($var, $value = NULL, $escape = TRUE) {

		if (is_array($var)) {

			// include plugin
			platcode()->plugins->include_class('static/ArrayStream');

			if (ArrayStream::is_assoc($var)) {
				// copy data
				foreach ($var as $name => $val)
					$this->set($name, $val);
			}
			else {
				// INPUT object
				$input = &platcode()->plugins->import('Input');
				// copy data from input
				foreach($var as $field) {
					if (preg_match('/^((post|get|cookie):)?(.+)$/', $field, $matches)) {
						// input type
						$type = empty($matches[2]) ? 'post' : $matches[2];
						// input value
						$name = $matches[3];
						// save it
						$this->input[$name] = array($input->$type($name), TRUE);
					}
					else
						throw new Platcode_Exception("Invalid input '$field' field for Active Query");
				}
			}
		}
		else
			// save field
			$this->input[$var] = array($value, $escape);

		return $this;
	}

	/**
	 * Select fields
	 * @param mixed $fields
	 * @param boolean $escape
	 * @return ActiveQuery
	 */
	public function columns($fields, $escape = TRUE) {

		if (is_string($fields)) {

			if ($fields == '*')
				return;

			$fields = preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY);
		}

		foreach ($fields as $f)
			array_push($this->fields, $this->escape($f, $escape));
			
		return $this;
	}

	/**
	 * Set where condition
	 * @param mixed $var
	 * @param mixed $value
	 * @param array $properties
	 * @return ActiveQuery
	 */
	public function where($var, $value = NULL, $properties = array()) {

		if (is_array($var)) {
			foreach($var as $key => $val)
				$this->where($key, $val);
		}
		else
			// set condition properties
			$this->condition[$var] = array($value, array_merge(
				array(
					'operator' => '=',
					'term' => 'AND',
					'escape' => TRUE,
					'level' => 0
				),
				$properties
			));

		return $this;
	}

	/**
	 * Get where string
	 * @return string
	 */
	private function compile_condition() {
		$where = '';
		$curlevel = 0;
		$need_term = FALSE;

		foreach ($this->condition as $key => $var) {

			// set brackets
			if ($var[1]['level'] > $curlevel) {

				if ($need_term) {
					$where .= ' '.$var[1]['term'].' ';
					$need_term = FALSE;
				}
				// add open bracket
				$where .= str_repeat('(', $var[1]['level']);
			}
			else if ($var[1]['level'] < $curlevel)
				$where .= ')';

			if ($need_term)
				$where .= ' '.$var[1]['term'].' ';

			// set condition
			$where .= $this->escape($key).' '.$var[1]['operator'].' '.$this->escape_param($var[0], $var[1]['escape']);

			// set loop flags
			$need_term = TRUE;
			$curlevel = $var[1]['level'];
		}

		// set operation
		if ( ! empty($where)) {

			// close all brackets
			while($curlevel-- > 0)
				$where .= ')';

			$where = ' WHERE '.$where;
		}

		return $where;
	}

	/**
	 * Fetch "order by" rule
	 * @return string
	 */
	private function order_rule() {

		$rules = array();

		foreach($this->order as $field => $rule) {
			array_push($rules, $field.' '.$rule);
		}

		return empty($rules) ? '' : ' ORDER BY '.implode(',', $rules);
	}

	/**
	 * Get SELECT query
	 * @return string
	 */
	public function select() {

		$columns = '*';

		if ( ! empty($this->fields))
			$columns = implode(',', $this->fields);

		return 'SELECT '.$columns.' FROM '.implode(',', $this->tables).$this->compile_condition().$this->order_rule();
	}

	/**
	 * Get INSERT query
	 * @return string
	 */
	public function insert() {

		$values = array();

		foreach ($this->input as $field => $var)
			array_push($values, $this->escape_param($var[0], $var[1]));

		$columns = implode(',', array_keys($this->input));

		return 'INSERT INTO '.$this->tables[0].' ('.$columns.') VALUES('.implode(',', $values).')';
	}

	/**
	 * Get UPDATE query
	 * @return string
	 */
	public function update() {

		$values = array();

		foreach($this->input as $field => $var)
			array_push($values, $field.' = '.$this->escape_param($var[0], $var[1]));

		return 'UPDATE '.$this->tables[0].' SET '.implode(',', $values).$this->compile_condition();
	}

	/**
	 * Get DELETE query
	 * @return string
	 */
	public function delete() {
		return 'DELETE FROM '.$this->tables[0].$this->compile_condition();
	}

	/**
	 * Call a function
	 * @param string $function_name
	 * @return string
	 */
	public function call($function_name) {
		// if exist parameters
		if ( ! empty($this->input)) {

			// function arguments
			$parameters = array();

			foreach($this->input as $field => $var)
				array_push($parameters, $this->escape_param($var[0], $var[1]));

			$args = implode(',', $parameters);
		}

		// SQL
		return 'SELECT * FROM '.$function_name.'('.$args.')'.$this->compile_condition().$this->order_rule();
	}

	/**
	 * Run Query
	 * @param PDOStatement $stmt
	 * @return boolean
	 */
	public function query(&$stmt) {
		// execute SQL
		return $stmt->execute($this->params);
	}
}

// end of file